package cn.mulanbay.gateway.handler;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import cn.mulanbay.common.bean.ResultBean;
import cn.mulanbay.common.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * 自定义限流、熔断异常处理
 *
 * @author fenghong
 */
public class SentinelFallbackHandler implements WebExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(SentinelFallbackHandler.class);

    public SentinelFallbackHandler() {
    }

    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange,ResultBean rb) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        byte[] errorInfo  = JsonUtil.beanToJson(rb).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(errorInfo);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        logger.error("ex exception:"+ex.getClass());
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }
        if (!BlockException.isBlockException(ex)) {
            return Mono.error(ex);
        }
        ResultBean rb = this.createMessage(ex);
        String path = exchange.getRequest().getPath().pathWithinApplication().value();
        this.addLog(path,rb);
        return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange,rb));
    }

    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }

    /**
     * 添加日志
     * @param path
     * @param rb
     */
    private void addLog(String path,ResultBean rb){
        logger.error("sentinel 异常,path:{},code:{},message:{}",path,rb.getCode(),rb.getMessage());
    }

    /**
     * 创建异常消息
     * @param e
     * @return
     */
    private ResultBean createMessage(Throwable e){
        // 自定义封装的类，可以定义
        if (e instanceof FlowException) {
            // 限流
            return new ResultBean(5001,"请求过于频繁，请稍后再试");
        } else if (e instanceof DegradeException) {
            // 降级
            return new ResultBean(5002,"服务降级");
        } else if (e instanceof ParamFlowException) {
            // 热点参数
            return new ResultBean(5003,"热点参数限流");
        } else if (e instanceof SystemBlockException) {
            // 系统保护
            return new ResultBean(5004,"触发系统保护规则");
        } else if (e instanceof AuthorityException) {
            // 授权规则
            return new ResultBean(5005,"授权规则不通过");
        } else{
            return new ResultBean(5000,"系统处理异常");
        }
    }
}
